//===--- ScalarPairTypeInfo.h - Type info for scalar pairs ------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines ScalarPairTypeInfo, which is a convenient abstract
// implementation of TypeInfo for working with types that are
// efficiently scalarizable.
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_IRGEN_INTERNAL_SCALARPAIRTYPEINFO_H
#define POLARPHP_IRGEN_INTERNAL_SCALARPAIRTYPEINFO_H

#include "polarphp/irgen/internal/NativeConventionSchema.h"
#include "polarphp/irgen/internal/ScalarTypeInfo.h"

namespace polar::irgen {

template <class Derived, class Base>
class ScalarPairTypeInfo : public ScalarTypeInfo<Derived, Base> {
   using super = ScalarTypeInfo<Derived, Base>;
protected:
   template <class... T> ScalarPairTypeInfo(T &&...args)
      : super(::std::forward<T>(args)...) {}

   const Derived &asDerived() const {
      return static_cast<const Derived &>(*this);
   }

public:
   llvm::StructType *getStorageType() const {
      return cast<llvm::StructType>(TypeInfo::getStorageType());
   }

   Address projectFirstElement(IRGenFunction &IGF, Address address) const {
      return IGF.Builder.CreateStructGEP(address, 0, Size(0),
                                         address->getName()
                                         + asDerived().getFirstElementLabel());
   }

   Address projectSecondElement(IRGenFunction &IGF, Address address) const {
      return IGF.Builder.CreateStructGEP(address, 1,
                                         asDerived().getSecondElementOffset(IGF.IGM),
                                         address->getName() + asDerived().getSecondElementLabel());
   }

   unsigned getExplosionSize() const override {
      return 2;
   }

   void getSchema(ExplosionSchema &schema) const override {
      llvm::StructType *structTy = getStorageType();
      schema.add(ExplosionSchema::Element::forScalar(structTy->getElementType(0)));
      schema.add(ExplosionSchema::Element::forScalar(structTy->getElementType(1)));
   }

   void addToAggLowering(IRGenModule &IGM, SwiftAggLowering &lowering,
                         Size offset) const override {
      llvm::StructType *structTy = getStorageType();
      this->addScalarToAggLowering(IGM, lowering, structTy->getElementType(0),
                                   offset, asDerived().getFirstElementSize(IGM));
      this->addScalarToAggLowering(IGM, lowering, structTy->getElementType(1),
                                   offset + asDerived().getSecondElementOffset(IGM),
                                   asDerived().getSecondElementSize(IGM));
   }

   void loadAsCopy(IRGenFunction &IGF, Address address,
                   Explosion &e) const override {
      Address firstAddr = projectFirstElement(IGF, address);
      auto first =
         IGF.Builder.CreateLoad(firstAddr, firstAddr->getName() + ".load");
      asDerived().emitRetainFirstElement(IGF, first);
      e.add(first);

      Address secondAddr = projectSecondElement(IGF, address);
      auto second = IGF.Builder.CreateLoad(secondAddr);
      asDerived().emitRetainSecondElement(IGF, second);
      e.add(second);
   }

   void loadAsTake(IRGenFunction &IGF, Address addr,
                   Explosion &e) const override {
      // Load the function.
      Address firstAddr = projectFirstElement(IGF, addr);
      e.add(IGF.Builder.CreateLoad(firstAddr));

      Address secondAddr = projectSecondElement(IGF, addr);
      e.add(IGF.Builder.CreateLoad(secondAddr));
   }

   void assign(IRGenFunction &IGF, Explosion &e, Address address,
               bool isOutlined) const override {
      // Store the function pointer.
      Address firstAddr = projectFirstElement(IGF, address);
      asDerived().emitAssignFirstElement(IGF, e.claimNext(), firstAddr);

      Address secondAddr = projectSecondElement(IGF, address);
      asDerived().emitAssignSecondElement(IGF, e.claimNext(), secondAddr);
   }

   void initialize(IRGenFunction &IGF, Explosion &e, Address address,
                   bool isOutlined) const override {
      Address firstAddr = projectFirstElement(IGF, address);
      IGF.Builder.CreateStore(e.claimNext(), firstAddr);

      Address secondAddr = projectSecondElement(IGF, address);
      IGF.Builder.CreateStore(e.claimNext(), secondAddr);
   }

   void copy(IRGenFunction &IGF, Explosion &src,
             Explosion &dest, Atomicity atomicity) const override {
      auto first = src.claimNext();
      asDerived().emitRetainFirstElement(IGF, first, atomicity);
      dest.add(first);

      auto second = src.claimNext();
      asDerived().emitRetainSecondElement(IGF, second, atomicity);
      dest.add(second);
   }

   void consume(IRGenFunction &IGF, Explosion &src,
                Atomicity atomicity) const override {
      auto first = src.claimNext();
      asDerived().emitReleaseFirstElement(IGF, first, atomicity);

      auto second = src.claimNext();
      asDerived().emitReleaseSecondElement(IGF, second, atomicity);
   }

   void fixLifetime(IRGenFunction &IGF, Explosion &src) const override {
      auto first = src.claimNext();
      if (!asDerived().isFirstElementTrivial())
         IGF.emitFixLifetime(first);

      auto second = src.claimNext();
      if (!asDerived().isSecondElementTrivial())
         IGF.emitFixLifetime(second);
   }

   void destroy(IRGenFunction &IGF, Address addr, PILType T,
                bool isOutlined) const override {
      if (!asDerived().isFirstElementTrivial()) {
         auto first = IGF.Builder.CreateLoad(projectFirstElement(IGF, addr));
         asDerived().emitReleaseFirstElement(IGF, first);
      }

      if (!asDerived().isSecondElementTrivial()) {
         auto first = IGF.Builder.CreateLoad(projectSecondElement(IGF, addr));
         asDerived().emitReleaseSecondElement(IGF, first);
      }
   }

   void packIntoEnumPayload(IRGenFunction &IGF,
                            EnumPayload &payload,
                            Explosion &src,
                            unsigned offset) const override {
      payload.insertValue(IGF, src.claimNext(), offset);
      payload.insertValue(IGF, src.claimNext(),
                          offset + asDerived().getSecondElementOffset(IGF.IGM).getValueInBits());
   }

   void unpackFromEnumPayload(IRGenFunction &IGF,
                              const EnumPayload &payload,
                              Explosion &dest,
                              unsigned offset) const override {
      auto storageTy = getStorageType();
      dest.add(payload.extractValue(IGF, storageTy->getElementType(0), offset));
      dest.add(payload.extractValue(IGF, storageTy->getElementType(1),
                                    offset + asDerived().getSecondElementOffset(IGF.IGM).getValueInBits()));
   }
};

} // polar::irgen

#endif // POLARPHP_IRGEN_INTERNAL_SCALARPAIRTYPEINFO_H
